#include "./BSP/SPI/bsp_spi.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/NORFLASH/bsp_norflash.h"
#include <stdio.h>

uint16_t g_norflash_type = W25Q16;      /* 默认是W25Q16 */

/**
 * @brief       获取NOR Flash信息字符串
 * @param       无
 * @retval      Flash信息字符串
 */
static const char* norflash_get_info_string(void)
{
    switch(g_norflash_type)
    {
        case W25Q80:  return "W25Q80 (1MB)";
        case W25Q16:  return "W25Q16 (2MB)";
        case W25Q32:  return "W25Q32 (4MB)";
        case W25Q64:  return "W25Q64 (8MB)";
        case W25Q128: return "W25Q128 (16MB)";
        case W25Q256: return "W25Q256 (32MB)";
        case BY25Q64: return "BY25Q64 (8MB)";
        case BY25Q128: return "BY25Q128 (16MB)";
        case NM25Q64: return "NM25Q64 (8MB)";
        default: return "Unknown Flash";
    }
}

/**
 * @brief       初始化SPI NOR FLASH
 * @param       无
 * @retval      无
 */
void norflash_init(void)
{
    uint8_t temp;

    NORFLASH_CS_GPIO_CLK_ENABLE();      /* 使能CS引脚时钟 */

    GPIO_InitTypeDef gpio_init_struct;
    gpio_init_struct.Pin = NORFLASH_CS_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;    /* 推挽输出模式 */
    gpio_init_struct.Pull = GPIO_PULLUP;            /* 上拉电阻 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;  /* 高速输出 */
    HAL_GPIO_Init(NORFLASH_CS_GPIO_PORT, &gpio_init_struct);

    NORFLASH_CS(1);                         /* 取消片选 */

    spi1_init();                            /* 初始化SPI1 */
    spi1_set_speed(SPI_SPEED_2);            /* SPI1设置为高速模式36MHz */
    
    g_norflash_type = norflash_read_id();   /* 读取FLASH ID */
    
    if (g_norflash_type == W25Q256)         /* W25Q256必须使能4字节地址模式 */
    {
        temp = norflash_read_sr(3);         /* 读取状态寄存器3，检查地址模式 */

        if ((temp & 0X01) == 0)             /* ADS位为0表示当前为3字节地址模式 */
        {
            norflash_write_enable();        /* 写使能 */
            temp |= 1 << 1;                 /* 设置ADP位，上电默认4字节地址模式 */
            norflash_write_sr(3, temp);     /* 写状态寄存器3 */
            
            NORFLASH_CS(0);                 /* 选中芯片 */
            spi1_read_write_byte(FLASH_Enable4ByteAddr);    /* 发送4字节地址使能指令 */
            NORFLASH_CS(1);                 /* 取消选中 */
        }
    }
    
    /* 显示NOR Flash详细信息 */
    NORFLASH_LOG_I("NOR Flash: %s, ID: 0x%04X", norflash_get_info_string(), g_norflash_type);
}

/**
 * @brief       等待FLASH芯片空闲
 * @param       无
 * @retval      无
 * @note        通过读取状态寄存器1的BUSY位判断芯片是否空闲
 *              BUSY位为1表示芯片忙碌，为0表示空闲
 */
static void norflash_wait_busy(void)
{
    while ((norflash_read_sr(1) & 0x01) == 0x01);   /* 等待BUSY位清零 */
}

/**
 * @brief       FLASH写使能
 * @param       无
 * @retval      无
 * @note        将状态寄存器1的WEL位置1，允许写操作
 */
void norflash_write_enable(void)
{
    NORFLASH_CS(0);                             /* 选中芯片 */
    spi1_read_write_byte(FLASH_WriteEnable);    /* 发送写使能指令 */
    NORFLASH_CS(1);                             /* 取消选中 */
}

/**
 * @brief       发送地址到FLASH芯片
 * @param       address: 要发送的地址
 * @retval      无
 * @note        根据芯片型号自动选择3字节或4字节地址模式
 *              W25Q256使用4字节地址，其他芯片使用3字节地址
 */
static void norflash_send_address(uint32_t address)
{
    if (g_norflash_type == W25Q256)                     /* 只有W25Q256支持4字节地址模式 */
    {
        spi1_read_write_byte((uint8_t)((address)>>24)); /* 发送 bit31 ~ bit24 地址 */
    } 
    spi1_read_write_byte((uint8_t)((address)>>16));     /* 发送 bit23 ~ bit16 地址 */
    spi1_read_write_byte((uint8_t)((address)>>8));      /* 发送 bit15 ~ bit8  地址 */
    spi1_read_write_byte((uint8_t)address);             /* 发送 bit7  ~ bit0  地址 */
}

/**
 * @brief       读取25QXX的状态寄存器，25QXX一共有3个状态寄存器
 * @param       regno: 状态寄存器号(1~3)
 * @retval      状态寄存器值
 * @note        状态寄存器1：
 *              BIT7  6   5   4   3   2   1   0
 *              SPR   RV  TB BP2 BP1 BP0 WEL BUSY
 *              SPR:默认0,状态寄存器保护位,配合WP使用
 *              TB,BP2,BP1,BP0:FLASH区域写保护设置
 *              WEL:写使能锁定
 *              BUSY:忙标记位(1,忙;0,空闲)
 *              默认:0x00
 *
 *              状态寄存器2：
 *              BIT7  6   5   4   3   2   1   0
 *              SUS   CMP LB3 LB2 LB1 (R) QE  SRP1
 *
 *              状态寄存器3：
 *              BIT7      6    5    4   3   2   1   0
 *              HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS
 */
uint8_t norflash_read_sr(uint8_t regno)
{
    uint8_t byte = 0, command = 0;

    switch (regno)
    {
        case 1:
            command = FLASH_ReadStatusReg1;  /* 读状态寄存器1指令 */
            break;
        case 2:
            command = FLASH_ReadStatusReg2;  /* 读状态寄存器2指令 */
            break;
        case 3:
            command = FLASH_ReadStatusReg3;  /* 读状态寄存器3指令 */
            break;
        default:
            command = FLASH_ReadStatusReg1;  /* 默认读状态寄存器1 */
            break;
    }

    NORFLASH_CS(0);                         /* 选中芯片 */
    spi1_read_write_byte(command);          /* 发送读寄存器指令 */
    byte = spi1_read_write_byte(0Xff);      /* 读取一个字节 */
    NORFLASH_CS(1);                         /* 取消选中 */
    
    return byte;
}

/**
 * @brief       写25QXX状态寄存器
 * @param       regno: 状态寄存器号(1~3)
 * @param       sr:要写入状态寄存器的值
 * @retval      无
 * @note        写状态寄存器前需要先执行写使能指令，寄存器说明见norflash_read_sr函数说明
 */
void norflash_write_sr(uint8_t regno, uint8_t sr)
{
    uint8_t command = 0;

    switch (regno)
    {
        case 1:
            command = FLASH_WriteStatusReg1;  /* 写状态寄存器1指令 */
            break;
        case 2:
            command = FLASH_WriteStatusReg2;  /* 写状态寄存器2指令 */
            break;
        case 3:
            command = FLASH_WriteStatusReg3;  /* 写状态寄存器3指令 */
            break;
        default:
            command = FLASH_WriteStatusReg1;  /* 默认写状态寄存器1 */
            break;
    }

    NORFLASH_CS(0);                         /* 选中芯片 */
    spi1_read_write_byte(command);          /* 发送写寄存器指令 */
    spi1_read_write_byte(sr);               /* 写入寄存器值 */
    NORFLASH_CS(1);                         /* 取消选中 */
}

/**
 * @brief       读取FLASH芯片ID
 * @param       无
 * @retval      芯片ID值
 * @note        返回16位芯片ID，用于识别芯片型号
 *              高8位为厂商ID，低8位为设备ID
 */
uint16_t norflash_read_id(void)
{
    uint16_t deviceid;

    NORFLASH_CS(0);                                 /* 选中芯片 */
    spi1_read_write_byte(FLASH_ManufactDeviceID);   /* 发送读ID指令 */
    spi1_read_write_byte(0);                        /* 发送3个字节地址 */
    spi1_read_write_byte(0);
    spi1_read_write_byte(0);
    deviceid = spi1_read_write_byte(0xFF) << 8;     /* 读取厂商ID */
    deviceid |= spi1_read_write_byte(0xFF);         /* 读取设备ID */
    NORFLASH_CS(1);                                 /* 取消选中 */

    return deviceid;
}

/**
 * @brief       从FLASH读取数据
 * @param       pbuf: 数据存储缓冲区
 * @param       addr: 读取起始地址(最大32bit)
 * @param       datalen: 读取数据长度(最大65535)
 * @retval      无
 * @note        在指定地址开始读取指定长度的数据，可以跨页读取，没有长度限制
 */
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;

    NORFLASH_CS(0);                         /* 选中芯片 */
    spi1_read_write_byte(FLASH_ReadData);   /* 发送读数据指令 */
    norflash_send_address(addr);            /* 发送读取地址 */
    
    for (i = 0; i < datalen; i++)
    {
        pbuf[i] = spi1_read_write_byte(0XFF);   /* 循环读取数据 */
    }
    
    NORFLASH_CS(1);                         /* 取消选中 */
}

/**
 * @brief       页写入数据到FLASH
 * @param       pbuf: 要写入的数据缓冲区
 * @param       addr: 写入起始地址(最大32bit)
 * @param       datalen: 写入数据长度(不超过256字节),该数不应该超过该页的剩余字节数!!!
 * @retval      无
 * @note        1. 单次最多写入256字节
 *              2. 不能跨页写入
 *              3. 写入前需要确保目标区域已擦除(为0xFF)
 */
static void norflash_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;

    norflash_write_enable();                /* 写使能 */

    NORFLASH_CS(0);                         /* 选中芯片 */
    spi1_read_write_byte(FLASH_PageProgram);/* 发送页编程指令 */
    norflash_send_address(addr);            /* 发送写入地址 */

    for (i = 0; i < datalen; i++)
    {
        spi1_read_write_byte(pbuf[i]);      /* 循环写入数据 */
    }
    
    NORFLASH_CS(1);                         /* 取消选中 */
    norflash_wait_busy();                   /* 等待写入完成 */
}

/**
 * @brief       无检验写FLASH
 * @param       pbuf: 要写入的数据缓冲区
 * @param       addr: 写入起始地址(最大32bit)
 * @param       datalen: 写入数据长度(最大65535)
 * @retval      无
 * @note        1. 必须确保写入区域已擦除(全为0xFF)
 *              2. 具有自动换页功能
 *              3. 写入前需确保地址不越界
 */
static void norflash_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t pageremain;
    pageremain = 256 - addr % 256;  /* 单页剩余的字节数 */

    if (datalen <= pageremain)      /* 不大于256个字节 */
    {
        pageremain = datalen;
    }

    while (1)
    {
        /* 当写入字节比页内剩余地址还少的时候, 一次性写完
         * 当写入直接比页内剩余地址还多的时候, 先写完整个页内剩余地址, 然后根据剩余长度进行不同处理
         */
        norflash_write_page(pbuf, addr, pageremain);

        if (datalen == pageremain)      /* 写入结束了 */
        {
            break;
        }
        else                            /* datalen > pageremain */
        {
            pbuf += pageremain;         /* pbuf指针地址偏移,前面已经写了pageremain字节 */
            addr += pageremain;         /* 写地址偏移,前面已经写了pageremain字节 */
            datalen -= pageremain;      /* 写入总长度减去已经写入了的字节数 */

            if (datalen > 256)          /* 剩余数据还大于一页,可以一次写一页 */
            {
                pageremain = 256;       /* 一次可以写入256个字节 */
            }
            else                        /* 剩余数据小于一页,可以一次写完 */
            {
                pageremain = datalen;   /* 不够256个字节了 */
            }
        }
    }
}

/**
 * @brief       带扇区擦除的写FLASH
 * @param       pbuf: 要写入的数据缓冲区
 * @param       addr: 写入起始地址(最大32bit)
 * @param       datalen: 写入数据长度(最大65535)
 * @retval      无
 * @note        1. 该函数带擦除操作，自动判断是否需要擦除
 *              2. SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 16个扇区为1个Block，擦除的最小单位为扇区(4KB)
 *              3. 写入前会自动检查目标区域是否已擦除
 */
uint8_t g_norflash_buf[4096];   /* 扇区缓存 */

void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t *norflash_buf;

    norflash_buf = g_norflash_buf;
    secpos = addr / 4096;       /* 扇区地址 */
    secoff = addr % 4096;       /* 在扇区内的偏移 */
    secremain = 4096 - secoff;  /* 扇区剩余空间大小 */

    //printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
    if (datalen <= secremain)
    {
        secremain = datalen;    /* 不大于4096个字节 */
    }

    while (1)
    {
        norflash_read(norflash_buf, secpos * 4096, 4096);   /* 读出整个扇区的内容 */

        for (i = 0; i < secremain; i++)     /* 校验数据 */
        {
            if (norflash_buf[secoff + i] != 0XFF)
            {
                break;                      /* 需要擦除, 直接退出for循环 */
            }
        }

        if (i < secremain)                  /* 需要擦除 */
        {
            norflash_erase_sector(secpos);  /* 擦除这个扇区 */

            for (i = 0; i < secremain; i++) /* 复制 */
            {
                norflash_buf[i + secoff] = pbuf[i];
            }

            norflash_write_nocheck(norflash_buf, secpos * 4096, 4096);  /* 写入整个扇区 */
        }
        else    /* 写已经擦除了的,直接写入扇区剩余区间. */
        {
            norflash_write_nocheck(pbuf, addr, secremain);              /* 直接写扇区 */
        }

        if (datalen == secremain)
        {
            break;  /* 写入结束了 */
        }
        else        /* 写入未结束 */
        {
            secpos++;               /* 扇区地址增1 */
            secoff = 0;             /* 偏移位置为0 */

            pbuf += secremain;      /* 指针偏移 */
            addr += secremain;      /* 写地址偏移 */
            datalen -= secremain;   /* 字节数递减 */

            if (datalen > 4096)
            {
                secremain = 4096;   /* 下一个扇区还是写不完 */
            }
            else
            {
                secremain = datalen;/* 下一个扇区可以写完了 */
            }
        }
    }
}

/**
 * @brief       擦除整个芯片
 * @param       无
 * @retval      无
 * @note        擦除整个芯片需要较长时间，请耐心等待
 */
void norflash_erase_chip(void)
{
    norflash_write_enable();    /* 写使能 */
    norflash_wait_busy();       /* 等待空闲 */
    NORFLASH_CS(0);
    spi1_read_write_byte(FLASH_ChipErase);  /* 发送整片擦除指令 */ 
    NORFLASH_CS(1);
    norflash_wait_busy();       /* 等待芯片擦除结束 */
}

/**
 * @brief       擦除一个扇区
 * @param       saddr: 扇区地址(扇区号，非字节地址)
 * @retval      无
 * @note        1. 扇区大小为4KB
 *              2. 擦除一个扇区的最少时间约150ms
 *              3. 擦除后扇区内容全为0xFF
 */
void norflash_erase_sector(uint32_t saddr)
{
    //printf("fe:%x\r\n", saddr);   /* 监视falsh擦除情况,测试用 */
    saddr *= 4096;              /* 转换为字节地址 */
    norflash_write_enable();    /* 写使能 */
    norflash_wait_busy();       /* 等待空闲 */

    NORFLASH_CS(0);
    spi1_read_write_byte(FLASH_SectorErase);    /* 发送扇区擦除指令 */
    norflash_send_address(saddr);               /* 发送扇区地址 */
    NORFLASH_CS(1);
    norflash_wait_busy();       /* 等待扇区擦除完成 */
}

/**
 * @brief       NORFLASH读写测试函数
 * @param       无
 * @retval      0: 测试通过  1: 测试失败
 * @note        测试内容包括：芯片ID检测、扇区擦除、页写入、数据读取、数据校验
 */
uint8_t norflash_test(void)
{
    uint16_t chip_id;
    uint32_t test_addr = 0x1000;    /* 测试地址：4KB处，避免影响重要数据 */
    uint16_t test_len = 256;        /* 测试数据长度：一页 */
    uint8_t write_buf[256];         /* 写入缓冲区 */
    uint8_t read_buf[256];          /* 读取缓冲区 */
    uint16_t i;
    uint8_t error_count = 0;
    
    printf("=== NORFLASH 测试开始 ===\r\n");
    
    /* 1. 读取芯片ID测试 */
    chip_id = norflash_read_id();
    g_norflash_type = chip_id;  /* 更新全局变量以便静态函数使用 */
    
    /* 显示Flash信息 */
    printf("NOR Flash: %s, ID: 0x%04X\r\n", norflash_get_info_string(), g_norflash_type);
    
    printf("1. 芯片ID检测: 通过\r\n");
    
    if (chip_id == 0 || chip_id == 0xFFFF)
    {
        printf("   芯片ID无效，SPI通信可能存在问题\r\n");
        return 1;
    }
    
    /* 2. 准备测试数据 */
    printf("2. 准备测试数据...\r\n");
    for (i = 0; i < test_len; i++)
    {
        write_buf[i] = i % 256;     /* 生成0-255循环的测试数据 */
    }
    
    /* 3. 擦除测试扇区 */
    printf("3. 擦除测试扇区(地址:0x%08lX)...\r\n", test_addr);
    norflash_erase_sector(test_addr / 4096);    /* 扇区号 = 地址 / 4096 */
    
    /* 4. 验证擦除结果 */
    printf("4. 验证擦除结果...\r\n");
    norflash_read(read_buf, test_addr, test_len);
    
    error_count = 0;
    for (i = 0; i < test_len; i++)
    {
        if (read_buf[i] != 0xFF)
        {
            error_count++;
            if (error_count <= 5)  /* 只显示前5个错误 */
            {
                printf("   擦除验证失败，地址0x%08lX，期望:0xFF，实际:0x%02X\r\n", 
                       test_addr + i, read_buf[i]);
            }
        }
    }
    
    if (error_count == 0)
    {
        printf("   擦除验证成功\r\n");
    }
    else
    {
        printf("   擦除验证失败，错误数量: %d\r\n", error_count);
        return 1;
    }
    
    /* 5. 写入测试数据 */
    printf("5. 写入测试数据(%d字节)...\r\n", test_len);
    norflash_write(write_buf, test_addr, test_len);
    
    /* 6. 读取并校验数据 */
    printf("6. 读取并校验数据...\r\n");
    norflash_read(read_buf, test_addr, test_len);
    
    error_count = 0;
    for (i = 0; i < test_len; i++)
    {
        if (read_buf[i] != write_buf[i])
        {
            error_count++;
            if (error_count <= 5)  /* 只显示前5个错误 */
            {
                printf("   数据校验失败，地址0x%08lX，期望:0x%02X，实际:0x%02X\r\n", 
                       test_addr + i, write_buf[i], read_buf[i]);
            }
        }
    }
    
    if (error_count == 0)
    {
        printf("   数据校验成功\r\n");
    }
    else
    {
        printf("   数据校验失败，错误数量: %d\r\n", error_count);
        return 1;
    }
    
    /* 7. 跨页写入测试 */
    printf("7. 跨页写入测试...\r\n");
    uint32_t cross_page_addr = 0x1100 - 50;    /* 跨越页边界的地址 */
    uint16_t cross_page_len = 100;             /* 跨越页边界的长度 */
    
    /* 准备跨页测试数据 */
    for (i = 0; i < cross_page_len; i++)
    {
        write_buf[i] = 0xAA + (i % 16);        /* 生成测试模式 */
    }
    
    norflash_write(write_buf, cross_page_addr, cross_page_len);
    norflash_read(read_buf, cross_page_addr, cross_page_len);
    
    error_count = 0;
    for (i = 0; i < cross_page_len; i++)
    {
        if (read_buf[i] != write_buf[i])
        {
            error_count++;
            if (error_count <= 5)  /* 只显示前5个错误 */
            {
                printf("   跨页测试失败，地址0x%08lX，期望:0x%02X，实际:0x%02X\r\n", 
                       cross_page_addr + i, write_buf[i], read_buf[i]);
            }
        }
    }
    
    if (error_count == 0)
    {
        printf("   跨页写入测试成功\r\n");
    }
    else
    {
        printf("   跨页写入测试失败，错误数量: %d\r\n", error_count);
        return 1;
    }
    
    /* 8. 速度测试 */
    printf("8. 读写速度测试...\r\n");
    uint32_t start_time, end_time;
    uint32_t write_speed, read_speed;
    
    /* 写入速度测试 */
    start_time = HAL_GetTick();
    norflash_write(write_buf, test_addr, test_len);
    end_time = HAL_GetTick();
    
    if (end_time > start_time)
    {
        write_speed = (test_len * 1000) / (end_time - start_time);
        printf("   写入速度: %d字节用时%ldms (约%ldB/s)\r\n", 
               test_len, end_time - start_time, write_speed);
    }
    else
    {
        printf("   写入速度: %d字节用时<1ms\r\n", test_len);
    }
    
    /* 读取速度测试 */
    start_time = HAL_GetTick();
    norflash_read(read_buf, test_addr, test_len);
    end_time = HAL_GetTick();
    
    if (end_time > start_time)
    {
        read_speed = (test_len * 1000) / (end_time - start_time);
        printf("   读取速度: %d字节用时%ldms (约%ldB/s)\r\n", 
               test_len, end_time - start_time, read_speed);
    }
    else
    {
        printf("   读取速度: %d字节用时<1ms\r\n", test_len);
    }
    
    printf("=== NORFLASH 测试完成 - 所有测试通过 ===\r\n\r\n");
    return 0;
}
